Skip to content

Silence ERROR log on override_properties recovery path#1

Closed
pszypowicz wants to merge 1 commit into
devfrom
fix/473-quiet-override-properties
Closed

Silence ERROR log on override_properties recovery path#1
pszypowicz wants to merge 1 commit into
devfrom
fix/473-quiet-override-properties

Conversation

@pszypowicz
Copy link
Copy Markdown
Owner

Summary

Service.configure_char calls Characteristic.override_properties to install a restricted valid_values set, then set_value to seed an initial value. override_properties is designed to recover gracefully when the characteristic's existing _value (the format default, set in __init__) falls outside the newly-restricted valid_values - it catches the ValueError and falls back via _get_default_value().

The validator it uses, valid_value_or_raise, calls logger.error() before raising. So even though the recovery path is the intended behavior, every consumer that restricts valid_values for a characteristic whose format-default is excluded leaks a spurious ERROR log line.

This is what users hit at every HomeKit accessory build for alarm panels that do not advertise ARM_HOME: the SecuritySystemCurrentState and SecuritySystemTargetState defaults to StayArmed=0, but the configured valid_values excludes 0. The existing recovery still kicks in correctly (HA's HomeKit accessory works fine), but the log fills with red entries that look like real failures.

Root cause

# pyhap/characteristic.py - override_properties (before)
try:
    self.value = self.to_valid_value(self._value)
    self.valid_value_or_raise(self._value)   # logs ERROR before raising
except ValueError:
    self.value = self._get_default_value()    # silent recovery (intended)

valid_value_or_raise is appropriate when the caller wants the validation error surfaced (e.g. set_value), but inside override_properties the exception is by design caught and ignored - so the ERROR log is misleading.

Fix

Replace the try/except-around-validator pattern with an explicit silent membership check. valid_value_or_raise itself is left unchanged so other callers retain ERROR-level logging.

# after
try:
    candidate = self.to_valid_value(self._value)
except ValueError:
    self.value = self._get_default_value()
    return

valid_values_dict = self._properties.get(PROP_VALID_VALUES)
if valid_values_dict and candidate not in valid_values_dict.values():
    self.value = self._get_default_value()
else:
    self.value = candidate

Behavior matrix is unchanged:

to_valid_value candidate in restricted valid_values result
raises - fall back to default
ok no fall back to default
ok yes adopt candidate

Verification

  • pytest tests/ passes 188/188 relevant tests (one unrelated pre-existing failure on Python 3.14 in test_accessory_driver.py::test_start_from_sync due to upstream's use of removed asyncio.SafeChildWatcher).
  • Smoke-tested the patched characteristic.py against a live Home Assistant Core install with a HomeKit-bridged alarm panel. The two pyhap.characteristic ERROR lines that appeared on every HA restart are gone; the HomeKit accessory continues to function (state changes propagate, target state changes from the Home app reach the panel).

Related

PR scope note

Opening this against the fork's dev branch first as a self-review checkpoint before considering an upstream PR.

override_properties is designed to recover gracefully when the
characteristic's existing value falls outside a newly-restricted
valid_values set (e.g. SecuritySystemCurrentState defaults to
StayArmed=0, but a consumer configures the characteristic for an
alarm panel that does not advertise ARM_HOME and so removes 0 from
valid_values). The recovery is silent from the caller's perspective:
ValueError is caught and self.value is reset via _get_default_value().

The validator it relied on, valid_value_or_raise, still calls
logger.error() before raising, leaking an "is an invalid value" line
into the application log even though the situation is expected and
recovered. Downstream this manifests in Home Assistant as spurious
pyhap.characteristic ERRORs at every HomeKit accessory build for
alarm panels that restrict StayArmed (issue ikalchev#473
and home-assistant/core#130564, #156142).

Replace the try/except-around-validator pattern in override_properties
with an explicit silent membership check. valid_value_or_raise itself
is unchanged so callers like set_value (where ERROR-level logging is
appropriate) keep their behavior.
@pszypowicz
Copy link
Copy Markdown
Owner Author

Superseded by the upstream PR ikalchev#491. Closing this fork-internal checkpoint.

@pszypowicz pszypowicz closed this May 22, 2026
@pszypowicz
Copy link
Copy Markdown
Owner Author

The fix is now proposed upstream as ikalchev#491 - please follow and review it there. This fork PR was only a self-review checkpoint, so it's closed in favor of the upstream one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant